﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Security.Cryptography;
using System.Threading;

namespace GeoPal
{
    /// <summary>
    /// Class that communicates with the server
    /// </summary>
    public class Communicator
    {
        /// <summary>
        /// Class that stores the state of the asynchronous request
        /// </summary>
        public class RequestState
        {
            /// <summary>
            /// Holds the request
            /// </summary>
            public WebRequest Request;
            /// <summary>
            /// Store any data in this
            /// </summary>
            public object Data;
            /// <summary>
            /// Holds the UrlString to match up results (Database lookup, etc).
            /// </summary>
            public string SiteUrl;
            /// <summary>
            /// Constructor of the RequestState class
            /// </summary>
            /// <param name="request">Request</param>
            /// <param name="data">Object Data</param>
            /// <param name="siteUrl">URL string</param>
            public RequestState(WebRequest request, object data, string siteUrl)
            {
                this.Request = request;
                this.Data = data;
                this.SiteUrl = siteUrl;
            }
        }
        /// <summary>
        /// Delegate that updates contacts
        /// </summary>
        public UpdateContactsDelegate updateContactsCallback;
        /// <summary>
        /// Delegate that updates messages
        /// </summary>
        public UpdateMessagesDelegate updateMessagesCallback;
        /// <summary>
        /// Delegate that updates the status message
        /// </summary>
        public UpdateStatusMessageDelegate updateStatusMessageCallback;
        /// <summary>
        /// Delegate that updates the connection status
        /// </summary>
        public UpdateConnectionStatusDelegate updateConnectionStatusCallback;
        /// <summary>
        /// Delegate that updates the map
        /// </summary>
        public MapRefreshDelegate mapRefreshCallback;

        private long sentBytes;
        private long receivedBytes;

        /// <summary>
        /// Overall sent bytes
        /// </summary>
        public long SendBytes
        {
            get {return sentBytes;}
        }

        /// <summary>
        /// Overall received bytes
        /// </summary>
        public long ReceivedBytes
        {
            get {return receivedBytes;}
        }

        /// <summary>
        /// Constructor of the Communicator class (creates blank)
        /// </summary>
        public Communicator()
        {
            sentBytes = 0;
            receivedBytes = 0;
        }

        /// <summary>
        /// Hashes an input string and returns the hash as a 32 character hexadecimal string.
        /// </summary>
        /// <param name="input">Input string</param>
        /// <returns>Hashed string</returns>
        private static string getMd5Hash(string input)
        {
            // Create a new instance of the MD5CryptoServiceProvider object.
            MD5 md5Hasher = MD5.Create();

            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            return sBuilder.ToString();
        }
        /// <summary>
        /// Begins to request the contact list from the server
        /// </summary>
        /// <param name="settings">Settings object with user credentials</param>
        public void GetContactList(Settings settings)
        {
            try
            {
                updateStatusMessageCallback("Gathering contact data...");
                updateConnectionStatusCallback(Status.ConnectionStatus.Progress);
                GeoService.GeoService geoService = new GeoPal.GeoService.GeoService();
                geoService.Timeout = 10000;
                geoService.Url = settings.ServerUrl + "services.php";
                geoService.BeginGetContactList(settings.UserName, getMd5Hash(settings.UserPass), new AsyncCallback(GetContactListCallback), geoService);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Handles the contact list update response
        /// </summary>
        /// <param name="result">Asynchronous event result</param>
        public void GetContactListCallback(IAsyncResult result)
        {
            try
            {
                GeoService.GeoService geoService = (GeoService.GeoService)result.AsyncState;
                GeoService.Contact[] contacts = geoService.EndGetContactList(result);
                updateContactsCallback(contacts);
                updateStatusMessageCallback("Contact list updated.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Connected);
                mapRefreshCallback();

            }
            catch (System.Web.Services.Protocols.SoapException e)
            {
                switch (e.Message)
                {
                    case "LOGIN_ERROR": updateStatusMessageCallback("Invalid username/password!"); break;
                    case "EMPTY_ERROR": updateStatusMessageCallback("No contacts."); break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Begins to report the position to the server
        /// </summary>
        /// <param name="settings">Settings object with user credentials</param>
        /// <param name="gpsData">Gps object with position data</param>
        public void UpdatePosition(Settings settings, Gps gpsData)
        {
            try
            {
                updateStatusMessageCallback("Updating position...");
                updateConnectionStatusCallback(Status.ConnectionStatus.Progress);
                GeoService.GeoService geoService = new GeoPal.GeoService.GeoService();
                geoService.Timeout = 10000;
                geoService.Url = settings.ServerUrl + "services.php";
                geoService.BeginUpdatePosition(settings.UserName, getMd5Hash(settings.UserPass), gpsData.position.lastGPRMC, settings.IsLogged, new AsyncCallback(UpdatePositionCallback), geoService);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Handles the position report response
        /// </summary>
        /// <param name="result">Asynchronous event result</param>
        public void UpdatePositionCallback(IAsyncResult result)
        {
            try
            {
                GeoService.GeoService geoService = (GeoService.GeoService)result.AsyncState;
                string queryResult = geoService.EndUpdatePosition(result);
                // TODO: itt van egy exception ("Client found response content type of 'text/html', but expected 'text/xml'"), aminek nagyon nem kéne lennie! Lehetséges, hogy .NET CF bug, nem saját
                updateConnectionStatusCallback(Status.ConnectionStatus.Connected);
                switch (queryResult)
                {
                    case "UPDATED": updateStatusMessageCallback("Position updated."); break;
                    case "CREATED": updateStatusMessageCallback("Position set."); break;
                    default: updateStatusMessageCallback("Illegal operation."); break;
                }
            }
            catch (System.Web.Services.Protocols.SoapException e)
            {
                switch (e.Message)
                {
                    case "LOGIN_ERROR": updateStatusMessageCallback("Invalid username/password!"); break;
                    case "QUERY_ERROR":
                    default: updateStatusMessageCallback("Illegal operation."); break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
/*                UpdateMessageCallback("Couldn't connect to server.");
                UpdateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);*/
            }
        }
        /// <summary>
        /// Begins to send a message
        /// </summary>
        /// <param name="settings">Settings object with user credentials</param>
        /// <param name="nameUserTo">Username of the recipient</param>
        /// <param name="message">Message text</param>
        public void SendMessage(Settings settings, string nameUserTo, string message)
        {
            try
            {
                GeoService.GeoService geoService = new GeoPal.GeoService.GeoService();
                geoService.Timeout = 10000;
                geoService.Url = settings.ServerUrl + "services.php";
                geoService.BeginSendMessage(settings.UserName, getMd5Hash(settings.UserPass), nameUserTo, message, new AsyncCallback(SendMessageCallback), geoService);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
            }
        }
        /// <summary>
        /// Handles the message sending response
        /// </summary>
        /// <param name="result">Asynchronous event result</param>
        public void SendMessageCallback(IAsyncResult result)
        {
            try
            {
                GeoService.GeoService geoService = (GeoService.GeoService)result.AsyncState;
                string queryResult = geoService.EndSendMessage(result);
                if (queryResult == "SENT") updateStatusMessageCallback("Message sent.");
            }
            catch (System.Web.Services.Protocols.SoapException e)
            {
                switch (e.Message)
                {
                    case "LOGIN_ERROR": updateStatusMessageCallback("Invalid username/password!"); break;
                    case "ILLEGAL_OPERATION": updateStatusMessageCallback("Illegal operation."); break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Begins to request message list from the server
        /// </summary>
        /// <param name="settings">Settings object with user credentials</param>
        public void GetMessages(Settings settings)
        {
            try
            {
                updateStatusMessageCallback("Downloading messages...");
                updateConnectionStatusCallback(Status.ConnectionStatus.Progress);
                GeoService.GeoService geoService = new GeoPal.GeoService.GeoService();
                geoService.Timeout = 10000;
                geoService.Url = settings.ServerUrl + "services.php";
                geoService.BeginGetMessages(settings.UserName, getMd5Hash(settings.UserPass), new AsyncCallback(GetMessagesCallback), geoService);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Handles the message request response
        /// </summary>
        /// <param name="result">Asynchronous event result</param>
        public void GetMessagesCallback(IAsyncResult result)
        {
            try
            {
                GeoService.GeoService geoService = (GeoService.GeoService)result.AsyncState;
                GeoService.Message[] messages = geoService.EndGetMessages(result);
                updateMessagesCallback(messages);
                updateStatusMessageCallback("Messages refreshed.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Connected);
                // TODO: itt hívni DeleteReadMessages()-t
                mapRefreshCallback();
            }
            catch (System.Web.Services.Protocols.SoapException e)
            {
                switch (e.Message)
                {
                    case "LOGIN_ERROR": updateStatusMessageCallback("Invalid username/password!"); break;
                    case "EMPTY_ERROR": updateStatusMessageCallback("No contacts."); break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Begins to request adding a new follower
        /// </summary>
        /// <param name="settings">Settings object with user credentials</param>
        /// <param name="friend">Username of the new follower</param>
        public void AddFollower(Settings settings, string friend)
        {
            try
            {
                updateConnectionStatusCallback(Status.ConnectionStatus.Progress);
                GeoService.GeoService geoService = new GeoPal.GeoService.GeoService();
                geoService.Timeout = 10000;
                geoService.Url = settings.ServerUrl + "services.php";
                geoService.BeginAddFollower(settings.UserName, getMd5Hash(settings.UserPass), friend, new AsyncCallback(AddFollowerCallback), geoService);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }
        /// <summary>
        /// Handles the new follower request response
        /// </summary>
        /// <param name="result">Asynchronous event result</param>
        public void AddFollowerCallback(IAsyncResult result)
        {
            try
            {
                GeoService.GeoService geoService = (GeoService.GeoService)result.AsyncState;
                string res = geoService.EndAddFollower(result);
                updateStatusMessageCallback("Messages refreshed.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Connected);
            }
            catch (System.Web.Services.Protocols.SoapException e)
            {
                switch (e.Message)
                {
                    case "LOGIN_ERROR": updateStatusMessageCallback("Invalid username/password!"); break;
                    case "QUERY_ERROR": updateStatusMessageCallback("Query error."); break;
                    case "NO_SUCH_USER": updateStatusMessageCallback("No such user."); break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                updateStatusMessageCallback("Couldn't connect to server.");
                updateConnectionStatusCallback(Status.ConnectionStatus.Disconnected);
            }
        }

    }
}
